﻿' VisioUtility.vb
' compile with: /doc:VisioUtility.xml
' <copyright>Copyright (c) Microsoft Corporation. All rights reserved.
' </copyright>
' <summary>This file contains utility classes and methods that perform
' operations commonly performed on Visio objects.</summary>

Imports System
Imports System.Diagnostics
Imports System.Resources
Imports System.Diagnostics.CodeAnalysis
' wpf file dialogs are merely wrappers for win32
Imports System.Windows.Forms
Imports System.Reflection

Imports Microsoft.Office.Interop.Visio
Imports Microsoft.Office.Interop.VisOcx
Imports AxMicrosoft.Office.Interop.VisOcx

Imports NetworkPlanSample

''' <summary>The Utility class contains various utility methods.</summary>
Public NotInheritable Class Utility

    ''' <summary>Create a global resource manager for the application
    ''' to retrieve localized strings.</summary>
    Private Shared theResourceManager As ResourceManager

    Private Sub New()
    End Sub

    ''' <summary>The MapVisioToWindows method converts Visio coordinates to
    ''' Windows coordinates.</summary>
    ''' <remarks>The conversion includes changing from Visio's coordinate
    ''' system (inches) to the Windows coordinate system (pixels). The
    ''' conversion of the Y coordinate must also take into the account the
    ''' different locations of the origin, since Visio's origin is at the
    ''' lower-left corner and the Windows origin is at the upper-left
    ''' corner.</remarks>
    ''' <param name="drawingControl">Drawing control with the Visio window
    ''' to use.</param>
    ''' <param name="visioX">X position in the Visio coordinate system.
    ''' </param>
    ''' <param name="visioY">Y position in the Visio coordinate system.
    ''' </param>
    ''' <returns>Point containing the given Visio coordinate in Windows
    ''' coordinates.</returns>
    <CLSCompliant(False)> _
    Public Shared Function MapVisioToWindows _
            (ByVal drawingControl As AxMicrosoft.Office.Interop.VisOcx.AxDrawingControl, _
             ByVal visioX As Double, ByVal visioY As Double) _
             As System.Drawing.Point

        ' The drawing control object must be valid.
        If drawingControl Is Nothing Then

            ' Throw a meaningful error.
            Throw New ArgumentNullException("drawingControl")
        End If

        Dim windowsX As Integer = 0
        Dim windowsY As Integer = 0
        Dim visioLeft As Double
        Dim visioTop As Double
        Dim visioWidth As Double
        Dim visioHeight As Double
        Dim pixelLeft As Integer
        Dim pixelTop As Integer
        Dim pixelWidth As Integer
        Dim pixelHeight As Integer
        Dim referenceWindow As Window

        referenceWindow = DirectCast(drawingControl.Window, Window)
        If referenceWindow Is Nothing Then
            Return System.Drawing.Point.Empty
        End If
        ' Get the window coordinates in Visio units.
        referenceWindow.GetViewRect(visioLeft, visioTop, visioWidth, visioHeight)

        ' Get the window coordinates in pixels.
        referenceWindow.GetWindowRect(pixelLeft, pixelTop, pixelWidth, pixelHeight)

        ' Convert the X coordinate by using pixels per inch from the
        ' width values.
        windowsX = CInt((pixelLeft + ((pixelWidth / visioWidth) * (visioX - visioLeft))))

        ' Convert the Y coordinate by using pixels per inch from the
        ' height values and transform from a top-left origin (windows
        ' coordinates) to a bottom-left origin (Visio coordinates).
        windowsY = CInt((pixelTop + ((pixelHeight / visioHeight) * (visioTop - visioY))))

        Return New System.Drawing.Point(windowsX, windowsY)
    End Function

    ''' <summary>The GetClickedShape method finds a shape at the specified
    ''' location within a default tolerance.</summary>
    ''' <param name="drawingControl">Drawing control with the Visio page
    ''' containing the location.</param>
    ''' <param name="clickLocationX">The X coordinate of the location in
    ''' Visio page units (inches).</param>
    ''' <param name="clickLocationY">The Y coordinate of the location in
    ''' Visio page units (inches).</param>
    ''' <returns>The Visio shape at the location or null if no shape is
    ''' found.</returns>
    <CLSCompliant(False)> _
    Public Shared Function GetClickedShape _
            (ByVal drawingControl As AxMicrosoft.Office.Interop.VisOcx.AxDrawingControl, _
             ByVal clickLocationX As Double, _
             ByVal clickLocationY As Double) _
             As IVShape

        Const Tolerance As Double = 0.0001

        Return GetClickedShape(drawingControl, clickLocationX, clickLocationY, Tolerance)
    End Function

    ''' <summary>The GetClickedShape method finds a shape at the specified
    ''' location.</summary>
    ''' <remarks>If there are more than one shape at the location the top
    ''' shape in Z order is returned.</remarks>
    ''' <param name="drawingControl">Drawing control with the Visio page
    ''' containing the location.</param>
    ''' <param name="clickLocationX">The X coordinate of the location in
    ''' Visio page units (inches).</param>
    ''' <param name="clickLocationY">The Y coordinate of the location in
    ''' Visio page units (inches).</param>
    ''' <param name="tolerance">The distance from the location to an
    ''' included shape.</param>
    ''' <returns>The Visio shape at the location or null if no shape is
    ''' found.</returns>
    <CLSCompliant(False)> _
    Public Shared Function GetClickedShape _
            (ByVal drawingControl As AxMicrosoft.Office.Interop.VisOcx.AxDrawingControl, _
             ByVal clickLocationX As Double, _
             ByVal clickLocationY As Double, _
             ByVal tolerance As Double) _
             As IVShape

        If drawingControl Is Nothing Then
            Return Nothing
        End If

        ' The drawing control object must be valid.
        If drawingControl.Window Is Nothing Then
            ' do nothing...and nothing can be done as well.
            Return Nothing
        End If

        Dim foundShape As IVShape = Nothing

        Dim currentPage As Page

        currentPage = drawingControl.Window.PageAsObj

        ' Use the spatial search method to return a list of
        ' shapes at the location.
        Dim foundShapes As Selection = currentPage.SpatialSearch(clickLocationX, clickLocationY, CShort(VisSpatialRelationCodes.visSpatialContainedIn), tolerance, CShort(VisSpatialRelationFlags.visSpatialFrontToBack))

        If foundShapes.Count > 0 Then

            ' The selection collection of shapes is 1-based, so
            ' the first shape is at index 1.
            foundShape = foundShapes(1)
        End If

        Return foundShape
    End Function

    ''' <summary>The GetShapeFromArguments method returns a reference to
    ''' a shape given the command line arguments.</summary>
    ''' <param name="visioApplication">The Visio application.</param>
    ''' <param name="arguments">The command line arguments string containing:
    ''' /doc=id /page=id /shape=sheet.id.</param>
    ''' <returns>The Visio shape or null.</returns>
    <CLSCompliant(False)> _
    Public Shared Function GetShapeFromArguments _
            (ByVal visioApplication As Microsoft.Office.Interop.Visio.IVApplication, _
             ByVal arguments As String) _
             As Shape

        If visioApplication Is Nothing Then
            Return Nothing
        End If

        If arguments Is Nothing Then
            Return Nothing
        End If

        Const equal As Char = "="c
        Const argumentDelimiter As Char = "/"c

        ' Standard Visio add-on command line arguments.
        Const commandLineArgumentDoc As String = "doc"
        Const commandLineArgumentPage As String = "page"
        Const commandLineArgumentShape As String = "shape"

        Dim index As Integer
        Dim docId As Integer = -1
        Dim pageId As Integer = -1
        Dim shapeId As String = ""
        Dim contextParts As String()
        Dim contextPart As String
        Dim argumentParts As String()
        Dim document As Document = Nothing
        Dim page As Page = Nothing
        Dim targetShape As Shape = Nothing

        ' Parse the command line arguments.
        contextParts = arguments.Trim().Split(argumentDelimiter)

        For index = contextParts.GetLowerBound(0) To contextParts.GetUpperBound(0)

            contextPart = contextParts(index).Trim()

            If contextPart.Length > 0 Then

                ' Separate the parameter from the parameter value.
                argumentParts = contextPart.Split(equal)

                If argumentParts.GetUpperBound(0) = 1 Then

                    ' Get the doc, page, and shape argument values.
                    If commandLineArgumentDoc.Equals(argumentParts(0)) Then

                        docId = Convert.ToInt16(argumentParts(1), System.Globalization.CultureInfo.InvariantCulture)
                    ElseIf commandLineArgumentPage.Equals(argumentParts(0)) Then

                        pageId = Convert.ToInt16(argumentParts(1), System.Globalization.CultureInfo.InvariantCulture)
                    ElseIf commandLineArgumentShape.Equals(argumentParts(0)) Then

                        shapeId = argumentParts(1)
                    End If
                End If
            End If
        Next

        ' If the command line arguments contains document, page, and shape
        ' then look up the shape.
        If (docId > 0) AndAlso (pageId > 0) AndAlso (shapeId.Length > 0) Then

            document = visioApplication.Documents(docId)
            page = document.Pages(pageId)
            targetShape = page.Shapes(shapeId)
        End If

        Return targetShape
    End Function

    ''' <summary>The OpenStencil method opens the specified Visio document
    ''' stencil.</summary>
    ''' <param name="drawingControl">Drawing control with the collection of
    ''' Visio documents to add the new stencil to.</param>
    ''' <param name="stencilPath">The stencil path\filename to open.</param>
    ''' <returns>Document object if the stencil is opened successfully.
    ''' A COMException is thrown if the stencil cannot be opened.</returns>
    <CLSCompliant(False)> _
    Public Shared Function OpenStencil _
            (ByVal drawingControl As AxMicrosoft.Office.Interop.VisOcx.AxDrawingControl, _
             ByVal stencilPath As String) _
             As Document

        Dim targetStencil As Document = Nothing

        If drawingControl Is Nothing Then
            Throw New ArgumentNullException("drawingControl")
        End If

        ' The drawing control object must be valid.
        If drawingControl.Window Is Nothing OrElse drawingControl.Window.Application Is Nothing Then

            ' Throw a meaningful error.
            Throw New ArgumentNullException("drawingControl")
        End If

        Try
            Dim targetDocuments As Documents
            targetDocuments = DirectCast(drawingControl.Window.Application.Documents, Documents)

            ' Open the stencil file invisibly.
            ' A COMException will be thrown if the open fails.
            ' If the stencil is currently open Visio will return a 
            ' reference to the open stencil.
            targetStencil = targetDocuments.OpenEx(stencilPath, CShort(VisOpenSaveArgs.visOpenRO) Or CShort(VisOpenSaveArgs.visOpenHidden) Or CShort(VisOpenSaveArgs.visOpenMinimized) Or CShort(VisOpenSaveArgs.visOpenNoWorkspace))

        Catch ex As Exception

            Dim errWin As ErrorWindow = New ErrorWindow("construct", ex)
            errWin.ShowDialog()

        End Try

        Return (targetStencil)

    End Function

    ''' <summary>The GetMaster method gets the master by name.</summary>
    ''' <param name="drawingControl">Drawing control with the collection of
    ''' Visio documents that contain the stencil and masters.</param>
    ''' <param name="stencilPath">The stencil path\filename.</param>
    ''' <param name="masterNameU">The universal name of the master.</param>
    ''' <returns>Master object if found. A COMException is thrown if not found.</returns>
    <CLSCompliant(False)> _
    Public Shared Function GetMaster _
            (ByVal drawingControl As AxMicrosoft.Office.Interop.VisOcx.AxDrawingControl, _
             ByVal stencilPath As String, _
             ByVal masterNameU As String) _
             As Master

        ' The drawing control object must be valid.
        If drawingControl Is Nothing Then

            ' Throw a meaningful error.
            Throw New ArgumentNullException("drawingControl")
        End If

        Dim targetMaster As Master = Nothing
        Dim targetDocument As Document
        Dim targetMasters As Masters = Nothing
        targetDocument = OpenStencil(drawingControl, stencilPath)
        targetMasters = targetDocument.Masters

        Try

            targetMaster = targetMasters.ItemU(masterNameU)

        Catch ex As Exception
        End Try

        ' the example stencils used universal names with embedded spaces
        ' which is forbidden when naming wpm objects, so we take the spaces out
        ' and test to see if the standard name is more acceptable (which we renamed)
        If targetMaster Is Nothing And targetMasters IsNot Nothing Then
            Dim strNewMaster As String = Replace(masterNameU, " ", "")
            targetMaster = targetMasters.Item(strNewMaster)
        End If

        Return (targetMaster)

    End Function

    ''' <summary>The SaveDrawing method prompts the user to save changes
    ''' to the Visio document.</summary>
    ''' <param name="drawingControl">Drawing control with the Visio
    ''' document to save.</param>
    ''' <param name="promptFirst">Display "save changes" prompt.</param>
    ''' <returns>The id of the message box button that dismissed
    ''' the dialog.</returns>
    <CLSCompliant(False)> _
    Public Shared Function SaveDrawing _
        (ByVal drawingControl As AxMicrosoft.Office.Interop.VisOcx.AxDrawingControl, _
         ByVal promptFirst As Boolean) _
         As DialogResult

        If drawingControl Is Nothing Then
            Return DialogResult.Cancel
        End If


        Dim saveFileDialog As SaveFileDialog = Nothing
        Dim result As DialogResult = DialogResult.No
        Dim targetFilename As String = String.Empty
        Dim targetDocument As Document

        Try
            targetFilename = drawingControl.Src
            targetDocument = DirectCast(drawingControl.Document, Document)

            ' Prompt to save changes.
            If promptFirst = True Then

                Dim prompt As String = String.Empty
                Dim title As String = String.Empty

                title = Utility.GetResourceString(Strings.SaveDialogTitle)

                If targetFilename Is Nothing Then
                    Return DialogResult.Cancel
                End If

                ' Save changes to the existing drawing.
                If targetFilename.Length > 0 Then
                    prompt = Utility.GetResourceString(Strings.SavePrompt)
                    prompt += Environment.NewLine
                    prompt += targetFilename
                Else

                    ' Save changes as new drawing.
                    prompt = Utility.GetResourceString(Strings.SaveAsPrompt)
                End If
                result = Utility.RtlAwareMessageBoxShow(Nothing, prompt, title, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, drawingControl.Window.Application.AlertResponse)
            Else
                result = DialogResult.Yes
            End If

            ' Display a file browse dialog to select path and filename.
            If (DialogResult.Yes = result) AndAlso (targetFilename.Length = 0) Then

                ' Set up the save file dialog and let the user specify the
                ' name to save the document to.
                saveFileDialog = New SaveFileDialog()
                saveFileDialog.Title = Utility.GetResourceString(Strings.SaveDialogTitle)
                saveFileDialog.Filter = Utility.GetResourceString(Strings.SaveDialogFilter)
                saveFileDialog.FilterIndex = 1
                saveFileDialog.InitialDirectory = System.Windows.Forms.Application.StartupPath

                If saveFileDialog.ShowDialog() = DialogResult.OK Then

                    targetFilename = saveFileDialog.FileName
                End If
            End If
            ' Save the document to the filename specified by
            ' the end user in the save file dialog, or the existing file name.
            If (DialogResult.Yes = result) AndAlso (targetFilename.Length > 0) Then

                If targetDocument IsNot Nothing Then
                    targetDocument.SaveAs(targetFilename)
                End If
                drawingControl.Src = targetFilename
                drawingControl.Document.Saved = True

            End If
        Finally

            ' Make sure the dialog is cleaned up.
            If saveFileDialog IsNot Nothing Then
                saveFileDialog.Dispose()
            End If
        End Try
        Return result
    End Function

    ''' <summary>This method displays the exception based on the
    ''' boolean value.</summary>
    ''' <param name="messageResourceName">Resource name of message to dispay.</param>
    ''' <param name="exception">Exception object.</param>
    ''' <param name="showErrorDialog">Display the dialog when true.</param>
    Public Shared Sub DisplayException _
            (ByVal messageResourceName As String, _
             ByVal exception As Exception, _
             ByVal showErrorDialog As Boolean)

        Dim alertResponse As Integer
        alertResponse = If(showErrorDialog, 0, -1)

        DisplayException(messageResourceName, exception, alertResponse)
    End Sub

    ''' <summary>This method displays the exception based on the
    ''' AlertResponse value.</summary>
    ''' <param name="messageResourceName">Resource name of message to dispay.</param>
    ''' <param name="exception">Exception object.</param>
    ''' <param name="alertResponse">AlertResponse value of the running
    ''' Visio instance.</param>
    Public Shared Sub DisplayException _
            (ByVal messageResourceName As String, _
             ByVal exception As Exception, _
             ByVal alertResponse As Integer)

        If exception Is Nothing Then
            Exit Sub
        End If

        Dim message As String
        message = GetResourceString(messageResourceName)

#If DEBUG Then
            ' Include call stack infomation when running a debug build.
            message += Environment.NewLine + Environment.NewLine
            message += exception.ToString()
#End If

        'Utility.RtlAwareMessageBoxShow(Nothing, message, exception.Source, MessageBoxButtons.OK, MessageBoxIcon.[Error], alertResponse)

        Dim errWin As ErrorWindow = New ErrorWindow(exception.Source.ToString, message)
        errWin.ShowDialog()

        If exception.TargetSite IsNot Nothing Then

            Dim strTargetSite As String = exception.TargetSite.ToString
            errWin.Msg = exception.TargetSite.ToString
            errWin.Ex = exception
            errWin.ShowDialog()

        End If

    End Sub

    ''' <summary>This method loads the string from the embedded resource.
    ''' </summary>
    ''' <param name="resourceName">Name of the resource to be loaded.</param>
    ''' <returns>Loaded resource string if successful, otherwise empty
    ''' string.</returns>
    Public Shared Function GetResourceString _
                (ByVal resourceName As String) _
                As String

        Dim resourceValue As String = ""

        Try


            If theResourceManager Is Nothing Then
                theResourceManager = New ResourceManager(Strings.ResourceFilename, System.Reflection.Assembly.GetExecutingAssembly())
            End If


            resourceValue = theResourceManager.GetString(resourceName, System.Globalization.CultureInfo.CurrentUICulture)

        Catch ex As Exception

            Dim errWin As System.Windows.Window = New ErrorWindow(ex)
            errWin.ShowDialog()

        End Try



        Return resourceValue
    End Function

    ''' <summary>The ProductId method reads the product ID from
    ''' the given shape.</summary>
    ''' <param name="productShape">The Visio shape representing a product.
    ''' </param>
    ''' <returns>The product ID read from the shape. If there
    ''' is an error, the returned ID is an invalid value.</returns>
    <CLSCompliant(False)> _
    Public Shared Function ProductId _
                (ByVal productShape As IVShape) _
                As Integer

        ' The shape object must be valid.
        If productShape Is Nothing Then

            ' Throw a meaningful error.
            Throw New ArgumentNullException("productShape")
        End If

        Dim retproductId As Integer = ProductData.InvalidProductId

        Dim productIdCell As Cell = Nothing

        ' Ignore shapes that do not have the product ID cell.
        If productShape.CellExistsU(Strings.ProductIdCellName, CInt(VisExistsFlags.visExistsAnywhere)) <> 0 Then

            ' Get the cell containing the product ID from the shape.
            productIdCell = productShape.CellsU(Strings.ProductIdCellName)

            ' Since product IDs are integers, get the cell value as an
            ' integer without casting or rounding.
            retproductId = productIdCell.ResultInt(VisUnitCodes.visNoCast, 0)
        End If

        Return retproductId
    End Function

    ''' <summary>class for right-to-left aware message box</summary>
    Public Shared Function RtlAwareMessageBoxShow _
            (ByVal owner As IWin32Window, _
             ByVal text As String, _
             ByVal caption As String, _
             ByVal buttons As MessageBoxButtons, _
             ByVal icon As MessageBoxIcon, _
             ByVal alertResponse As Integer) _
             As DialogResult

        If alertResponse <> 0 Then
            Return DirectCast(alertResponse, DialogResult)
        End If

        Dim control As Control = TryCast(owner, Control)
        Dim bRightToLeft As Boolean = False
        Dim options As MessageBoxOptions = DirectCast(0, MessageBoxOptions)
        If control IsNot Nothing Then
            bRightToLeft = (control.RightToLeft = RightToLeft.Yes)
        Else
            ' Ask the CurrentUICulture if we are running under right-to-left.
            bRightToLeft = System.Globalization.CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft
        End If

        If bRightToLeft Then
            options = options Or MessageBoxOptions.RtlReading Or MessageBoxOptions.RightAlign
        End If

        Return MessageBox.Show(owner, text, caption, buttons, icon, MessageBoxDefaultButton.Button1, _
        options)
    End Function
End Class

